Skip to content

feat(ui-web): bring the chat view to the Figma design - #710

Merged
gloryfromca merged 1 commit into
mainfrom
feat/chat_view_figma_design
Sep 23, 2026
Merged

gloryfromca merged 1 commit into
mainfrom
feat/chat_view_figma_design

Conversation

@xfng-sd

@xfng-sd xfng-sd commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Brings the web chat view to the Figma chat design system (Raven file: UserMessage, AssistantMessage, the Thinking compositions, dates between long-gap messages, and the flattened layout of message types).

Messages

  • The user bubble is the design's: feat(proactive_engine): add cron anti-runaway protection and missed-reminder notices #333, 12px radius, 1.8 line height (40px for one line). Files sent with a message are small tags inside the bubble (PDF / Word badge, plain glyph otherwise); pictures still stand above it.
  • A reply is a flat white card, 16px padding, one 12px gap between its parts. Prose is 14/1.8 with 8px paragraph gaps; headings are 16/22 semibold in the sans face.
  • Tables: one half-pixel rule boxing the table at a 12px radius and dividing every cell, a grey head row, 40px rows of 13/26 text. Long cells wrap; the head row does not.
  • Code listings: the same box, with a 40px header naming the language as < CSS >.

Process and thinking

  • The turn fold is one quiet 11px line with the turn's duration ("Done 1m16s"); opened, it shows the process lines at 11px with 12px glyphs from the design's icon set (search, read skill, use skill, run command, write, error), 8px apart. Diff counts keep green for additions and red for deletions.
  • A live thought starts folded, so a turn opens with the design's small thinking card (40px, a shimmering "Thinking..." line; its caret appears on hover). Clicking it opens the thought, which still follows its newest line.
  • Mid-turn narration stays, set at 12px between the process lines and the answer.

Dates

  • A centred "Today 10:58" / "Yesterday 10:58" / date line appears between two questions more than 30 minutes apart. Asks now carry their epoch time (AskData.at) for this.

Delivered files

  • Each delivery is a compact card under the reply (flattened layout): the type's page-with-a-fold mark (PPT / MD / HTML tones from the design, nearest family for the rest) or, where the file has one, a small first-page / image thumbnail; name and size; and the design's actions: open in a browser for HTML, download for binaries, open in the panel otherwise. The whole card still opens the file in the panel. The scaled markdown / code miniatures and their fetch are removed. The file-changes list keeps a card of its own below.

Syntax highlighting

  • Listings that name a language are coloured with Shiki 4: the synchronous core with the JavaScript regex engine and a fixed grammar set (C, CSS, diff, Dockerfile, Go, HTML, INI, Java, JavaScript, JSON, Markdown, Python, Rust, shell, SQL, TOML, TSX, TypeScript, XML, YAML; C++ reads with the C grammar, JSX with TSX). Colours are page tokens (--code-*, light and dark) set to the design's roles. Unknown or unnamed languages render as before.
  • Bundle cost: the IIFE grows from about 1.25 MB to 2.44 MB (gzip 388 KB to 570 KB); dist/index.html from 1.69 MB to 2.90 MB. The grammars are what it costs; the page is one inlined file, so they cannot load on demand.
  • The HTML / XML grammars carry a literal <!--, which breaks an inlined script. A build-only Vite step rewrites it to \x3C!-- after minification (it occurs only in string, template and regex literals, where it is the same character).

Other

  • The desk grid no longer sets overflow: hidden, which clipped its panes' drop shadows.
  • New catalogue keys: gui.transcript.date_today, gui.transcript.date_yesterday, gui.arts.browser, gui.arts.download; gui.think.live gains an ellipsis. ui-tui's generated catalogue is regenerated.
  • lone-delivery-css is replaced by delivery-picture-css, which pins the same invariant for the new card (the picture is sized from its height and never cropped). The class-namespace pins for the transcript went down and are lowered.

Type

  • Fix
  • Feature
  • Docs
  • CI / tooling
  • Refactor
  • Other

Verification

  • npx tsc --noEmit -p tsconfig.json (ui-web): pass

  • npx eslint . (ui-web): 0 errors (4 existing warnings in files this PR does not touch)

  • npx vitest run (ui-web): 2873 passed, 1 failed. The failure is settings-drawer-css ("sizes classes the markup actually carries"), which fails on main too on Windows: the gate builds the path D:\D:\... and cannot scan it.

  • node scripts/check-css.mjs, node scripts/check-class-namespace.mjs: OK

  • npm run build then uv run python ui-web/build.py then node ui-web/scripts/check-page.mjs: OK (boot snapshots match their goldens)

  • node ui-tui/scripts/gen-i18n.mjs --check: up to date

  • scripts/check_source_language.py origin/main..HEAD, scripts/check_large_files.py origin/main..HEAD: exit 0

  • Checked in the browser on ?stub=1 at 1440px against the Figma frames: one-line bubble 40px, card padding 16px, table rows 40px, file card 340x66, process lines 23px apart, thinking card 40px; light and dark themes; a desk pane's shadow drawing past the grid. The built dist page also boots offline with no console errors.

  • Relevant tests pass locally

  • Relevant lint / type checks pass locally

  • User-facing docs or screenshots are updated when needed

Risk

  • Security impact considered
  • Backward compatibility considered
  • Rollback path is clear for risky changes

User-visible: the whole chat transcript restyles; a live thought is folded by default; delivered markdown / code files show a type mark instead of a miniature. Highlighter output is built from escaped token text in spans whose colours come only from the theme, so no source markup reaches the page. The main operational cost is the page size above. Rollback is reverting this one squash commit; the page has no stored state that depends on it.

Related Issues

N/A

The transcript follows the Figma chat design system: a #333 bubble with
sent files as tags inside it, a flat white reply card with 16px padding,
11px process lines under a quiet fold header with the turn's duration,
the design's table and code-listing boxes, a centred date between two
questions far apart, and delivered files as compact cards (type mark or
first-page thumbnail, name, size, open / download / open-in-browser).

A live thought starts folded, as the design's small thinking card.
Listings that name a language are coloured with Shiki (sync core, JS
regex engine, a fixed grammar set), in page tokens set to the design's
roles; a build step escapes the grammars' literal "<!--" so the inlined
script still parses.

The desk grid no longer clips its panes' drop shadows.

Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blockers; this can merge as far as I am concerned.

I reviewed the current github/main...HEAD diff and the surrounding transcript, workspace delivery, history/live-session, prose-rendering, build, and styling paths. I also checked the repository rules and Web UI vocabulary/layering constraints, backward compatibility of restored and live asks, and the test edits for weakened coverage. The Shiki payload increase is substantial, but it is measured and explicitly disclosed in the PR description rather than hidden.

Verification on this revision:

  • npm test in ui-web: 208 files, 2,874 tests passed.
  • npm run type-check && npm run lint && npm run gen:check in ui-web: passed; lint reported only the four existing warnings in untouched files.
  • Production build, Python assembly, boot snapshots, check-page, check-css, and check-class-namespace: passed.
  • TUI generated-catalogue check, source-language gate, and large-file gate: passed.
  • CI checks shown on the PR are green.

@gloryfromca
gloryfromca merged commit cfe082b into main Sep 23, 2026
23 checks passed
@gloryfromca
gloryfromca deleted the feat/chat_view_figma_design branch September 23, 2026 09:04
LivXue added a commit that referenced this pull request Sep 23, 2026
…nk instead (#721)

## Summary

At the narrowest width the web UI lays out, the bar under the composer
field broke onto a second line and the send button dropped onto it,
left-aligned under the "+". The bar was a flex row allowed to wrap, and
a wrapping flex row picks its line breaks from its items' whole widths
before any of them shrinks. So a second line was the only answer it
had to a narrow column, and the send button, last in the row, was the
item that landed there.

Measured on main before this change, the range is wider than the 780px
case reported. With the rail open, the bar wrapped at every width from
the 780px floor to 820px (838px was the first width measured to fit),
and up to 940px once the usage meter shows text (980px fit). It also
wrapped at 780px in Chinese with the context ring shown, at 780px with
the rail at its widest (which leaves the chat column its 430px floor),
and at 1024px once a long folder name, a long model id, the context
ring and a populated meter shared the row.

The row no longer wraps, so no width can put the send button on a line
of its own. The four items whose width is their text (the folder, the
permission mode, the usage meter and the model) each take an equal
share of what the fixed controls leave, never more than their own
width, and each label ends in an ellipsis. Two decisions are worth
reviewing:

- Equal shares rather than proportional shrinking. Shrinking in
  proportion takes the most from the widest box, and a chip is mostly
  its icon and padding, so a short model name beside a long folder name
  went to nothing. Capped at its own width, a short name stays whole
  and only a long one gives: in Chinese at 780px with the context ring,
  both 48px labels stay whole and only the model name shrinks.
- The chip shrinks with the anchor it hangs in, and only its name
  clips. The anchor itself is not clipped, because the popover it holds
  stands outside the anchor's box.

Where the bar already fit, nothing moves: all 37 boxes in it measured
identical before and after in the 8 layouts where the old bar fit
(860px to 1440px, with and without the context ring, long names and a
populated meter). The sub-agent composer shares the `.under` rule and
loses its `flex-wrap` too. Its bar never wrapped, because its message
line is already `flex: 1`, and all 12 of its layouts measured are
identical before and after.

Not fixed here:

- The permission chip carries no hover title, so a shortened mode name
  is read in full only through its accessible name or its popover. The
  folder and model chips already carry one.
- At the tightest combination (780px window, rail at its widest,
  context ring and usage meter shown) each label keeps about 5px, so
  those chips read as their icons.
- Only Chrome was measured. The stylesheet already uses `:has()` in 28
  places, so this adds no browser requirement.

## Type

- [x] Fix
- [ ] Feature
- [ ] Docs
- [ ] CI / tooling
- [ ] Refactor
- [ ] Other

## Verification

In `ui-web/`, with `NODE_OPTIONS=--localstorage-file=<file>` so that
node 26 does not shadow happy-dom's `localStorage`. Everything below
ran on the final base, `cfe082bb0` (#710), unless it says otherwise.

- Red then green on the four new cases in `src/chrome/Dock.test.tsx`.
  With the stylesheet at the base, three fail: `never wraps`, `lets
  every box between a label and the bar give, and ends the label in an
  ellipsis`, and `shares what the controls leave equally, each at most
  its own width`. With the fix, the file passes 26 of 26. The fourth
  case, `keeps the controls that are not text at their own size`, is a
  guard and passes on both.
- Two mutants, each caught by exactly one case: widening the anchor
  selector so it also takes the "+" fails `keeps the controls that are
  not text at their own size`, and going back to proportional shrinking
  fails `shares what the controls leave equally, each at most its own
  width`.
- Every test file that reads `src/styles/page.css`, plus the CJK
  literal gate: 20 files, 474 tests passed (with `--testTimeout=30000`,
  for the load noted below).
- `npx vitest run --no-file-parallelism` on an earlier base,
  `3a463d6cd`: 206 files, 2858 passed, 4 failed, 1 unhandled error.
  None of them is this change's:
  - `features/desk/palette.test.ts` and `features/desk/store.test.ts`,
    their "when storage refuses" cases: the known node 26 local
    artifact, green in CI, failing the same way on the base.
  - `state/session/registry.test.ts`, cases of "the live session
    switch": 5000ms timeouts on a box at load average 48 to 90 (two in
    the full run, three with the file alone, the same on a tree
    byte-equal to the base). The file passes 28 of 28 with
    `--testTimeout=30000`, and CI's `ui rpc contract` job is green on
    that base.
  - an unhandled `ReferenceError: document is not defined` from a timer
    in `state/detail.ts` that fired after its test environment was torn
    down, attributed to `features/extAgents/ExtAgentsPage.test.tsx`. It
    does not reproduce with that file alone, on either tree, and
    neither file reads anything this change touches.
- `npm run type-check`: clean. `npm run lint`: 0 errors, 4 warnings,
  all `react-hooks/exhaustive-deps` in files this branch does not
  touch. `npm run gen:check`: matches the contract.
- `npm run build` then `python3 build.py`: both boot goldens match (290
  and 289 nodes). `check-page.mjs`, `check-css.mjs` and
  `check-class-namespace.mjs`: OK.

Gates: `commitlint --from origin/main --to HEAD`,
`scripts/check_commit_messages.py origin/main..HEAD`,
`scripts/check_large_files.py origin/main...HEAD`,
`scripts/check_source_language.py origin/main...HEAD` and
`pre-commit run --from-ref origin/main --to-ref HEAD` all exit 0, and
no hook modified a file.

The suite has no layout engine, so the bar was also measured in
headless Chrome against the fixture transport (`?stub=1`), built with
and without the fix from the final base. Per layout the probe reads
whether every item in the bar shares one row, the send button's offset
from the bar's right edge, overlap between neighbours, and any icon
spilling out of its chip:

| layouts measured | base | this change |
|---|---|---|
| English, 21, from 780px to 1440px | 15 wrap | 0 wrap |
| Chinese, 10, from 780px to 1440px | 3 wrap | 0 wrap |
| usage meter shown, 900 / 940 / 980px | 2 wrap | 0 wrap |
| where the old bar fit, 8 | - | 0 of 37 boxes move |
| sub-agent composer bar, 12 | - | identical |
| 3 popovers over shortened chips | - | anchored, in view |

The same measurements on the first base, `3a463d6cd`, gave the same
results.

- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally
- [x] User-facing docs or screenshots are updated when needed

No user-facing doc describes the bar, so none changed.

## Risk

At widths where the bar used to wrap, it now stays on one row and its
text labels shorten with an ellipsis instead; the send button stays at
the right end. Where the bar already fit, nothing moves.

The `.under` rule is shared with the sub-agent composer, which loses
`flex-wrap: wrap` too. Its layouts measured identical, since its
message line never let it wrap.

No security surface: stylesheet rules and one test file.

Rollback: revert the commit.

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

## Related Issues

N/A

Co-authored-by: Claude (claude-opus-5-5[1m]) <noreply@anthropic.com>
0xKT added a commit that referenced this pull request Sep 23, 2026
The redesigned transcript (#710) landed on main with its own icon map,
which still named the retired tool beside web_fetch. The entry goes; an
archived call renders through the drawn-glyph fallback like any other
name the map does not know.

Co-authored-by: Claude (claude-fable-5-1) <noreply@anthropic.com>
0xKT added a commit that referenced this pull request Sep 23, 2026
The redesigned transcript (#710) landed on main with its own icon map,
which still named the retired tool beside web_fetch. The entry goes; an
archived call renders through the drawn-glyph fallback like any other
name the map does not know.

Co-authored-by: Claude (claude-fable-5-1) <noreply@anthropic.com>
0xKT pushed a commit that referenced this pull request Sep 23, 2026
)

## Summary

The fold that stands in for a finished turn -- "done", and every line
inside it --
was set at 11px while the answer it sits with is 14px. Someone who opens
that fold
is reading the trail, not glancing past it, so type three sizes below
the copy
around it made them lean in for the one thing they had asked to see.

The head, the call rows, the stretch summary and the thought line all
move to 14px
on a 22px line. The parts that ride on a row move with them -- the
index, the clock,
the diff counts, the glyph -- so a row stays one piece rather than a
large label
with small furniture beside it. The badge pill goes to 12px instead: a
pill reads as
a notch below its row, not level with it.

Two decisions worth a reviewer's eye:

- The four line kinds (`.think`, `.tfh`, `.wrow.sum`, and `.wrow`) stay
declared
together. #710 put them in one rule on purpose, after they had drifted
to 13 and
13.5px in two different greys and read as three kinds of thing. Raising
only the
fold head would have recreated exactly that drift, so the whole kind
moves at once.
- The two comments that named the old numbers are rewritten. A comment
still saying
11px would misdescribe the rule directly under it, which is how the
previous drift
  went unnoticed.

This reverses the sizing part of #710 for these four line kinds only.
The greys, the
spacing, the glyph treatment and everything else that PR brought to the
Figma design
are untouched.

## Type

- [x] Fix
- [ ] Feature
- [ ] Docs
- [ ] CI / tooling
- [ ] Refactor
- [ ] Other

## Verification

One file changed, `ui-web/src/styles/page.css`, +22 -18. Run on the
branch, based on
`5c4ae70b6`:

```
npm test --prefix ui-web        208 files, 2890 tests passed
npm run lint --prefix ui-web    0 errors, 4 pre-existing warnings
npm run type-check --prefix ui-web   passed
npm run gen:check --prefix ui-web    generated.ts matches the contract (202 methods)
make check-large-files          passed
scripts/check_source_language.py github/main..HEAD   no findings
npx commitlint --from github/main --to HEAD          passed
```

Measured in a real browser rather than read off the stylesheet: the page
was built,
served on a live gateway, and `getComputedStyle` read back on an actual
finished
turn with its fold open and its rows expanded.

| | before | after | answer body |
|---|---|---|---|
| `.tfh` (done) | 11px / 15px | 14px / 22px | 14px |
| `.wrow` (call row) | 11px / 15px | 14px / 22px | 14px |
| `.wrow.sum` (summary) | 11px / 15px | 14px / 22px | 14px |
| `.think` (thought) | 11px / 15px | 14px / 22px | 14px |

- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally
- [ ] User-facing docs or screenshots are updated when needed

The last box is unchecked because no checked-in screenshot shows this
surface. The
before and after were captured locally to compare the two, and are not
committed --
this repo does not take image assets.

Note for anyone re-running the gates: `make check-source-language`
hardcodes
`origin/main..HEAD`, and in a checkout whose `origin` is the GitLab
mirror that
range covers a few hundred upstream commits and fails on their fixtures.
Run the
script with `github/main..HEAD` to lint this branch alone.

## Risk

Presentation only: no markup, no state, no behaviour. A finished turn's
trail
occupies more vertical space than it did, which is the point of the
change and the
one thing to look at.

Rollback is the single file: revert this commit.

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

## Related Issues

N/A

Co-authored-by: arelchan <204152633+arelchan@users.noreply.github.com>
Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants